/*
 * To the extent possible under law, the Fiji developers have waived
 * all copyright and related or neighboring rights to this tutorial code.
 *
 * See the CC0 1.0 Universal license for details:
 *     http://creativecommons.org/publicdomain/zero/1.0/
 */

import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

/**
 * ProcessPixels
 * <p/>
 * A template for processing each pixel of either
 * GRAY8, GRAY16, GRAY32 or COLOR_RGB images.
 *
 * @author The Fiji Team
 */
public class Process_Pixels implements PlugInFilter {
    protected ImagePlus image;

    // image property members
    private int width;
    private int height;

    // plugin parameters
    public double value;
    public String name;

    /**
     * @see ij.plugin.filter.PlugInFilter#setup(java.lang.String, ij.ImagePlus)
     */
    @Override
    public int setup(String arg, ImagePlus imp) {
        if (arg.equals("about")) {
            showAbout();
            return DONE;
        }

        image = imp;
        return DOES_8G | DOES_16 | DOES_32 | DOES_RGB;
    }

    /**
     * @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)
     */
    @Override
    public void run(ImageProcessor ip) {
        // get width and height
        width = ip.getWidth();
        height = ip.getHeight();

        if (showDialog()) {
            process(ip);
            image.updateAndDraw();
        }
    }

    private boolean showDialog() {
        GenericDialog gd = new GenericDialog("Process pixels");

        // default value is 0.00, 2 digits right of the decimal point
        gd.addNumericField("value", 0.00, 2);
        gd.addStringField("name", "John");

        gd.showDialog();
        if (gd.wasCanceled())
            return false;

        // get entered values
        value = gd.getNextNumber();
        name = gd.getNextString();

        return true;
    }

    /**
     * Process an image.
     * <p/>
     * Please provide this method even if {@link ij.plugin.filter.PlugInFilter} does require it;
     * the method {@link ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)} can only
     * handle 2-dimensional data.
     * <p/>
     * If your plugin does not change the pixels in-place, make this method return the results and
     * change the {@link #setup(java.lang.String, ij.ImagePlus)} method to return also the
     * <i>DOES_NOTHING</i> flag.
     *
     * @param image the image (possible multi-dimensional)
     */
    public void process(ImagePlus image) {
        // slice numbers start with 1 for historical reasons
        for (int i = 1; i <= image.getStackSize(); i++)
            process(image.getStack().getProcessor(i));
    }

    // Select processing method depending on image type
    public void process(ImageProcessor ip) {
        int type = image.getType();
        if (type == ImagePlus.GRAY8)
            process((byte[]) ip.getPixels());
        else if (type == ImagePlus.GRAY16)
            process((short[]) ip.getPixels());
        else if (type == ImagePlus.GRAY32)
            process((float[]) ip.getPixels());
        else if (type == ImagePlus.COLOR_RGB)
            process((int[]) ip.getPixels());
        else {
            throw new RuntimeException("not supported");
        }
    }

    // processing of GRAY8 images
    public void process(byte[] pixels) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // process each pixel of the line
                // example: add 'number' to each pixel
                pixels[x + y * width] += (byte) value;
            }
        }
    }

    // processing of GRAY16 images
    public void process(short[] pixels) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // process each pixel of the line
                // example: add 'number' to each pixel
                pixels[x + y * width] += (short) value;
            }
        }
    }

    // processing of GRAY32 images
    public void process(float[] pixels) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // process each pixel of the line
                // example: add 'number' to each pixel
                pixels[x + y * width] += (float) value;
            }
        }
    }

    // processing of COLOR_RGB images
    public void process(int[] pixels) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // process each pixel of the line
                // example: add 'number' to each pixel
                pixels[x + y * width] += (int) value;
            }
        }
    }

    public void showAbout() {
        IJ.showMessage("ProcessPixels",
                "a template for processing each pixel of an image"
        );
    }

    /**
     * Main method for debugging.
     * <p/>
     * For debugging, it is convenient to have a method that starts ImageJ, loads an
     * image and calls the plugin, e.g. after setting breakpoints.
     *
     * @param args unused
     */
    public static void main(String[] args) {
        // set the plugins.dir property to make the plugin appear in the Plugins menu
        Class<?> clazz = Process_Pixels.class;
        String url = clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class").toString();
        String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6);
        System.setProperty("plugins.dir", pluginsDir);

        // start ImageJ
        new ImageJ();

        // open the Clown sample
        ImagePlus image = IJ.openImage("http://imagej.net/images/clown.jpg");
        image.show();

        // run the plugin
        IJ.runPlugIn(clazz.getName(), "");
    }
}
